/* Susan(tm) Java Version
   by Thomas Lochmatter, Brig-Glis, Switzerland
   and Patrick Bizeau, Brig-Glis, Switzerland    */

package laWa;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Toolkit;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;

import javax.swing.JPanel;

import modelnew.Field;

// Shows a hexagonal field and lets play on it
public class SusanCanvas extends JPanel implements Runnable, MouseListener, ComponentListener, SusanGameListener {

  // ######################### INSTANCE VARIABLES
  
  /**
	 * 
	 */
	private static final long serialVersionUID = -6247637085626147400L;

// The middle points of the hexagons, used to draw them and to catch the click
  Point[] pt;

  // Integer array to store the hexagon edge points around [0,0]
  int[] hexax = {0,0,0,0,0,0};
  int[] hexay = {0,0,0,0,0,0};

  // The board
  Polygon boardpoly;

  // The stones
  int stoneradius;
  int[] shapepolygonx;
  int[] shapepolygony;
  int shapepolygonn;

  // The associated game
  public SusanGame game;

  // Some Listeners
  public SusanGameMultiListener sgml;

  // The arrow that shows the moves
  int arrowtimeout;
  Thread arrowthread;
  int arrowfrom;
  int arrowto;
  Polygon Arrow;

  // The game options
  public SusanOptions so;
  int playmode;
  Image imagebackground;

  // ######################### CONSTRUCTOR
  public SusanCanvas (SusanOptions so) {
    super();
    sgml=new SusanGameMultiListener();
    sgml.add(this);
    pt=new Point[121]; for(int i=0; i<121; i++) {pt[i]=new Point();}
    addMouseListener(this);
    addComponentListener(this);
    this.so=so;
    updateImages();
  }

  // ######################### BASIC FUNCTIONS

  // Starts a new game
  public boolean newGame(int overridebeginpl) {
    int beginpl;
    calcshapepolygon();
    playmode=so.playmode;

    if (overridebeginpl==SusanOptions.begin_random) {
      if (so.begin==SusanOptions.begin_random) {
        beginpl=(int)(Math.random()*2);
      } else {
        beginpl=so.begin;
      }
    } else {
      beginpl=overridebeginpl;
    }
    game=new SusanGame(beginpl, sgml);
    game.start();
    return true;
  }

  // Updates the game after changes in the SusanOptions
  // without starting a new game
  public boolean updateSusanOptions() {
    if (so.playmode==SusanOptions.playmode_analize) {
      playmode=so.playmode;
    }
    calcshapepolygon();
    if (so.showFortress) {
      if (game.calcFortress(game.playturn)) {repaint();}
    } else {
      if (game.hideFortress()) {repaint();}
    }
    if (so.showLoosers) {
      if (game.calcLoosers(game.playturn)) {repaint();}
    } else {
      if (game.hideLoosers()) {repaint();}
    }
    if (so.showVictims) {
      if (game.calcVictims(game.playturn)) {repaint();}
    } else {
      if (game.hideVictims()) {repaint();}
    }
    return true;
  }

  // Loads the images
  // If an image cannot be loaded, a unicolored image is created instead
  public boolean updateImages() {
    MediaTracker imagetracker = new MediaTracker(this);
    Toolkit tk=Toolkit.getDefaultToolkit();
    if (so.backgroundimage!=null) {
      imagebackground=tk.getImage(so.backgroundimage);
      imagetracker.addImage(imagebackground,0);
    }
    try {
    	imagetracker.waitForAll();
    } catch (InterruptedException e) {}
    if ((so.backgroundimage==null) || (imagetracker.isErrorID(0))) {imagebackground=getColorImage(so.backgroundcolor);}
    return true;
  }

  // Creates a unicolored image
  private Image getColorImage(Color cl) {
    Image img=new BufferedImage(100,100,BufferedImage.TYPE_3BYTE_BGR);
    Graphics g=img.getGraphics();
    g.setColor(cl);
    g.fillRect(0,0,100,100);
    return img;
  }

  public boolean canUseHistory() {
    return (so.playmode==SusanOptions.playmode_analize);
  }

  // ######################### COMPONENT EVENTS

  // Canvas resized, recalc the hexagon positions
  public void componentResized(ComponentEvent e) {
    Dimension d = getSize();
    int ww = Math.min((int)(d.width*0.75), (int)(d.height*0.95));
    int mx=(int)(d.width/2);
    int my=(int)(d.height/2);
    int dx=ww/16;
    int dy=ww/28;
    stoneradius=ww/24;

    int i=0;
    for(int y=-5; y<6; y++) {
      for(int x=-5; x<6; x++) {
        pt[i].x=mx+(2*x-y)*dx;
        pt[i].y=my+3*y*dy;
        i++;
      }
    }

    calcshapepolygon();

    hexax[0]=0;   hexay[0]=dy*2;
    hexax[1]=dx;  hexay[1]=dy;
    hexax[2]=dx;  hexay[2]=-dy;
    hexax[3]=0;   hexay[3]=-dy*2;
    hexax[4]=-dx; hexay[4]=-dy;
    hexax[5]=-dx; hexay[5]=dy;

    boardpoly=new Polygon();
    boardpoly.addPoint(mx-5*dx,my-13*dy);
    boardpoly.addPoint(mx-4*dx,my-14*dy);
    boardpoly.addPoint(mx-3*dx,my-13*dy);
    boardpoly.addPoint(mx-2*dx,my-14*dy);
    boardpoly.addPoint(mx-1*dx,my-13*dy);
    boardpoly.addPoint(mx     ,my-14*dy);
    boardpoly.addPoint(mx+1*dx,my-13*dy);
    boardpoly.addPoint(mx+2*dx,my-14*dy);
    boardpoly.addPoint(mx+3*dx,my-13*dy);
    boardpoly.addPoint(mx+4*dx,my-14*dy);

    boardpoly.addPoint(mx+5*dx,my-13*dy);
    boardpoly.addPoint(mx+5*dx,my-11*dy);
    boardpoly.addPoint(mx+6*dx,my-10*dy);
    boardpoly.addPoint(mx+6*dx,my-8*dy);
    boardpoly.addPoint(mx+7*dx,my-7*dy);
    boardpoly.addPoint(mx+7*dx,my-5*dy);
    boardpoly.addPoint(mx+8*dx,my-4*dy);
    boardpoly.addPoint(mx+8*dx,my-2*dy);
    boardpoly.addPoint(mx+9*dx,my-1*dy);

    boardpoly.addPoint(mx+9*dx,my+1*dy);
    boardpoly.addPoint(mx+8*dx,my+2*dy);
    boardpoly.addPoint(mx+8*dx,my+4*dy);
    boardpoly.addPoint(mx+7*dx,my+5*dy);
    boardpoly.addPoint(mx+7*dx,my+7*dy);
    boardpoly.addPoint(mx+6*dx,my+8*dy);
    boardpoly.addPoint(mx+6*dx,my+10*dy);
    boardpoly.addPoint(mx+5*dx,my+11*dy);
    boardpoly.addPoint(mx+5*dx,my+13*dy);

    boardpoly.addPoint(mx+4*dx,my+14*dy);
    boardpoly.addPoint(mx+3*dx,my+13*dy);
    boardpoly.addPoint(mx+2*dx,my+14*dy);
    boardpoly.addPoint(mx+1*dx,my+13*dy);
    boardpoly.addPoint(mx     ,my+14*dy);
    boardpoly.addPoint(mx-1*dx,my+13*dy);
    boardpoly.addPoint(mx-2*dx,my+14*dy);
    boardpoly.addPoint(mx-3*dx,my+13*dy);
    boardpoly.addPoint(mx-4*dx,my+14*dy);
    boardpoly.addPoint(mx-5*dx,my+13*dy);

    boardpoly.addPoint(mx-5*dx,my+11*dy);
    boardpoly.addPoint(mx-6*dx,my+10*dy);
    boardpoly.addPoint(mx-6*dx,my+8*dy);
    boardpoly.addPoint(mx-7*dx,my+7*dy);
    boardpoly.addPoint(mx-7*dx,my+5*dy);
    boardpoly.addPoint(mx-8*dx,my+4*dy);
    boardpoly.addPoint(mx-8*dx,my+2*dy);
    boardpoly.addPoint(mx-9*dx,my+1*dy);

    boardpoly.addPoint(mx-9*dx,my-1*dy);
    boardpoly.addPoint(mx-8*dx,my-2*dy);
    boardpoly.addPoint(mx-8*dx,my-4*dy);
    boardpoly.addPoint(mx-7*dx,my-5*dy);
    boardpoly.addPoint(mx-7*dx,my-7*dy);
    boardpoly.addPoint(mx-6*dx,my-8*dy);
    boardpoly.addPoint(mx-6*dx,my-10*dy);
    boardpoly.addPoint(mx-5*dx,my-11*dy);

    calcArrow();
    repaint();
  }

  // calculates the stone shape polygon
  private void calcshapepolygon() {
    if (so.shape==SusanOptions.shape_circle) {return;}

    int i=0;
    shapepolygonn=so.shapepolygon.npoints;
    shapepolygonx=new int[shapepolygonn];
    shapepolygony=new int[shapepolygonn];
    for (; i<shapepolygonn; i++) {
      shapepolygonx[i]=so.shapepolygon.xpoints[i]*stoneradius/100;
      shapepolygony[i]=so.shapepolygon.ypoints[i]*stoneradius/100;
    }
  }

  // Needed, but not needed
  public void componentHidden(ComponentEvent e) {}
  public void componentMoved(ComponentEvent e) {}
  public void componentShown(ComponentEvent e) {}

  // ######################### MOUSE HANDLING

  // Just released the mouse button
  public void mouseReleased(MouseEvent e) {
    if ((so.playmode==SusanOptions.playmode_net) && (game.playturn!=0)) {return;};

    int fl=getField(e.getPoint());
    if (game.Drag(fl)) {
      game.markDrag(Field.magicfield);
      this.repaint();
    //} else if (game.dragfrom!=Field.magicfield) {
    //  game.log("You can't place a stone here.");
    }
  }

  // Just pressed the mouse button
  public void mousePressed(MouseEvent e) {
    if ((so.playmode==SusanOptions.playmode_net) && (game.playturn!=0)) {
      
      return;
    };

    int fl=getField(e.getPoint());
    if (game.Drag(fl)) {
      game.markDrag(Field.magicfield);
      this.repaint();
    } else if (game.Place(fl)) {
      game.markDrag(Field.magicfield);
      this.repaint();
    } else if (game.canDrag(fl)) {
      if (game.markDrag(fl)) {this.repaint();};
    } else {
      
    }
  }

  // Needed, but not needed
  public void mouseClicked(MouseEvent e) {}
  public void mouseEntered(MouseEvent e) {}
  public void mouseExited(MouseEvent e) {}

  // Gets the field number from a point in the Canvas
  // Returns the magicfield value if the point is not in a hexagon
  private int getField(Point p) {
    int smin=hexay[0]*hexay[0];
    int imin=Field.magicfield;
    for(int i=0; i<121; i++) {
      int s=(pt[i].x-p.x)*(pt[i].x-p.x)+(pt[i].y-p.y)*(pt[i].y-p.y);
      if (smin>s) {smin=s; imin=i;}
    }
    return imin;
  }

  // ######################### ARROW METHODS

  // Sets the arrow
  public boolean setArrow(int from, int to, int timeout) {
    if ((arrowfrom==from) && (arrowto==to)) {return false;}
    arrowfrom=from;
    arrowto=to;
    calcArrow();
    if (timeout>0) {
      arrowtimeout=timeout;
      arrowthread=new Thread(this);
      arrowthread.start();
    }
    return true;
  }

  // Removes the arrow
  public synchronized boolean clearArrow() {
    if ((arrowfrom==Field.magicfield) && (arrowto==Field.magicfield)) {return false;}
    arrowfrom=Field.magicfield;
    arrowto=Field.magicfield;
    calcArrow();
    return true;
  }
 
  // Calculates the position of the arrow and generated a polygon object
  private void calcArrow() {
    if ((game.getField().field[arrowfrom] & Field.fieldexists)==0) {
      arrowfrom=Field.magicfield;
      if ((game.getField().field[arrowto] & Field.fieldexists)==0) {
        arrowto=Field.magicfield;
        Arrow=null;
        return;
      }

      Arrow=new Polygon();
      int scale=stoneradius/4;
      if (scale<1) {scale=1;}
      Arrow.addPoint(scale*3,-scale*12);
      Arrow.addPoint(scale,-scale*3);
      Arrow.addPoint(scale*5,-scale*4);
      Arrow.addPoint(0,scale);
      Arrow.addPoint(-scale*5,-scale*4);
      Arrow.addPoint(-scale,-scale*3);
      Arrow.addPoint(-scale*3,-scale*12);
      Arrow.addPoint(0,-scale*9);
      Arrow.translate(pt[arrowto].x,pt[arrowto].y);
    } else {
      int dx=pt[arrowfrom].x-pt[arrowto].x;
      int dy=pt[arrowfrom].y-pt[arrowto].y;
      int dd=(int)Math.sqrt(dx*dx+dy*dy)*6;
      dx=dx*stoneradius;
      dy=dy*stoneradius;

      Arrow=new Polygon();
      Arrow.addPoint((5*dx-2*dy)/dd,(5*dy+2*dx)/dd);
      Arrow.addPoint(6*(dx-dy)/dd,6*(dy+dx)/dd);
      Arrow.addPoint(0,0);
      Arrow.addPoint(6*(dx+dy)/dd,6*(dy-dx)/dd);
      Arrow.addPoint((5*dx+2*dy)/dd,(5*dy-2*dx)/dd);
      Arrow.translate(pt[arrowto].x,pt[arrowto].y);
      Arrow.addPoint(pt[arrowfrom].x,pt[arrowfrom].y);
    }
  }

  // Thread that deletes the arrow after a given timeout
  public synchronized void run() {
    Thread thisthread=Thread.currentThread();
    try {
      Thread.sleep(arrowtimeout);
    } catch (InterruptedException e) {}
    if (arrowthread==thisthread) {
      arrowthread=null;
      if (clearArrow()) {repaint();}
    }
  }

  // ######################### PAINT METHODS

  // Redraw the whole game canvas
  public void paint (Graphics g) {
    Dimension dd=getSize();
    int ww=imagebackground.getWidth(null);
    int hh=imagebackground.getHeight(null);
    for (int x=0; x<dd.width; x+=ww) {
      for (int y=0; y<dd.height; y+=hh) {
        g.drawImage(imagebackground,x,y,this);
      }
    }

    if (so.boardshadow) {
      g.setColor(so.boardshadowcolor);
      for (int i=0; i<4; i++) {
        boardpoly.translate(1,1);
        g.fillPolygon(boardpoly);
      }
      boardpoly.translate(-4,-4); 
    }

    for(int i=0; i<121; i++) {
      stonepaint(g,i);
    }
    if (Arrow!=null) {
      g.setColor(so.arrowcolor);
      g.fillPolygon(Arrow);
      g.setColor(so.arrowbordercolor);
      g.drawPolygon(Arrow);
    }
  }

  // Redraw one stone
  public void stonepaint(Graphics g, int i) {
    int fld=game.getField().field[i];
    if ((fld & Field.fieldexists)==0) {return;}

    Color bg;
    if ((fld & Field.fieldmarklost)!=0) {
      bg=so.lostcolor;
    } else if ((fld & Field.fieldmarklooser)!=0) {
      bg=so.loosercolor;
    } else if ((fld & Field.fieldmarkvictim)!=0) {
      bg=so.victimcolor;
    } else if ((fld & Field.fieldmarkfort)!=0) {
      bg=so.fortresscolor;
    } else {
      bg=so.boardcolor;
    }

    Polygon p=new Polygon(hexax, hexay, 6);
    p.translate(pt[i].x,pt[i].y);
    g.setColor(bg);
    g.fillPolygon(p);
    g.setColor(so.boardbordercolor);
    g.drawPolygon(p);

    if ((fld & Field.fieldoccupied)!=0) {
      if ((fld & Field.fieldplayer)!=0) {
        g.setColor(so.player2color);
      } else {
        g.setColor(so.player1color);
      }
      if (so.shape==SusanOptions.shape_circle) {
        g.fillOval(pt[i].x-stoneradius,pt[i].y-stoneradius,2*stoneradius+1,2*stoneradius+1);
      } else {
        Polygon shapepolygon=new Polygon(shapepolygonx,shapepolygony,shapepolygonn);
        shapepolygon.translate(pt[i].x,pt[i].y);
        g.fillPolygon(shapepolygon);
        g.drawPolygon(shapepolygon);
      }
    } else if ((fld & Field.fieldmarkdrag)!=0) {
      if (game.playturn!=0) {
        g.setColor(so.player2movecolor);
      } else {
        g.setColor(so.player1movecolor);
      }
      if (so.shape==SusanOptions.shape_circle) {
        g.drawOval(pt[i].x-stoneradius,pt[i].y-stoneradius,2*stoneradius,2*stoneradius);
      } else {
        Polygon shapepolygon=new Polygon(shapepolygonx,shapepolygony,shapepolygonn);
        shapepolygon.translate(pt[i].x,pt[i].y);
        g.drawPolygon(shapepolygon);
      }
    }
  }

  // ######################### SUSANGAME EVENTS

  // Occurs when a game finished
  public void SusanGameFinished(int winner) {
    if (so.showFortress) {
      if (game.calcFortress(winner)) {repaint();}
    }
  }
  
  // This method is called to do the preparations for the next turn.
  // Call the computer if it is its turn.
  public void SusanGamePrepareNextTurn(int player) {
    if ((playmode==SusanOptions.playmode_pc) && (player==1)) {
      game.moveComputer();
      if (game.getComputer().computerfrom==Field.magicfield) {
        
        game.Place(game.getComputer().computerto);
      } else {
        
        game.Drag(game.getComputer().computerfrom, game.getComputer().computerto);
      }
      setArrow(game.getComputer().computerfrom,game.getComputer().computerto,1000);
      this.repaint();
    } else {
      if (clearArrow()) {repaint();}
      if (so.showFortress) {
        if (game.calcFortress(player)) {repaint();}
      }
      if (so.showLoosers) {
        if (game.calcLoosers(player)) {repaint();}
      }
      if (so.showVictims) {
        if (game.calcVictims(player)) {repaint();}
      }
    }
  }

  // Not used events
  public void SusanGamePlacePerformed(int player, int to) {}
  public void SusanGameDragPerformed(int player, int from, int to) {}
  public void SusanGameComputerBegin() {}
  public void SusanGameComputerEnd() {}
  public void SusanGameComputerChangeStatus(String status) {}
  public void SusanGameLogEntry(String t) {}
  public void SusanGameHistoryChanged() {}
  public void SusanGameHistoryPositionChanged() {}
}

// ######################### END OF FILE
